package com.ruoyi.web.aspect;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;

/**
 * @ClassName: LogAspectConfig
 * @Description: 请求控制器切面
 * @author CMeeLin
 * @date 2022/10/11
 * 
 */
@Component
@Aspect
@Slf4j
public class ControllerLogAspect {

    @Autowired
    private ObjectMapper objectMapper;

    private static ThreadLocal<Long> startTime = new ThreadLocal<Long>();
    String controllerName;
    String method;

    private static List<String> sensitiveKey = new ArrayList<String>() {
        private static final long serialVersionUID = 1L;
        {
            this.add("password");
            this.add("oldpassword");
        }
    };

    /**
     * 定义拦截规则：拦截com..*.controller..*(..))包下面的所有类中，有@RequestMapping注解的方法
     */
    @Pointcut("execution(* com.ruoyi.web.controller..*.*(..))")
    public void webLog() {}

    /**
     * 请求方法前打印内容
     *
     * @param joinPoint
     * @throws JsonProcessingException
     * @throws IllegalAccessException
     * @throws UnsupportedEncodingException
     */
    @Before("webLog()")
    public void before(JoinPoint joinPoint) throws JsonProcessingException, IllegalAccessException {
        // 请求开始时间
        startTime.set(System.currentTimeMillis());
        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 获取请求头
        Enumeration<String> headerNames = request.getHeaderNames();
        Map<String, Object> headerMap = new HashMap<>(10);
        do {
            String header = headerNames.nextElement();
            headerMap.put(header, request.getHeader(header));
        } while (headerNames.hasMoreElements());

        controllerName = joinPoint.getSignature().getDeclaringTypeName();
        method = joinPoint.getSignature().getName();
        // 下面两个数组中，参数值和参数名的个数和位置是一一对应的。
        // Object[] objs = joinPoint.getArgs();

        // String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames(); // 参数名
        log.info("\n" + "请求地址  >>>  {}" + "\n" + "请求来源  >>>  {}" + "\n" + "请求方法  >>>  {}" + "\n" + "请求参数  >>>  {}"
            + "\n" + "参数简介  >>>  {}" + "\n" + "内容类型  >>>  {}" + "\n" + "请求头部  >>>  {}" + "\n" + "访问对象  >>>  {}" + "\n"
            + "访问方法  >>>  {}\n"

        // + "参数详情 >>> {}\n"
            , request.getRequestURL().toString(), request.getRemoteAddr(), request.getMethod(),
            getParams(request.getParameterMap()), joinPoint.getArgs(), request.getContentType(),
            objectMapper.writeValueAsString(headerMap), joinPoint.getSignature().getDeclaringTypeName(),
            joinPoint.getSignature().getName()
        // ,Arrays.toString(joinPoint.getArgs())
        // ,getParams(joinPoint.getArgs())
        );
    }

    private String getParams(Map<String, String[]> params) {
        StringBuilder sb = new StringBuilder();
        if (ObjectUtil.isNotEmpty(params)) {
            params.keySet().forEach(key -> {
                try {
                    sb.append(
                        "Arg[" + key + "]:" + getValue(key, objectMapper.writeValueAsString(params.get(key))) + ",");
                } catch (JsonProcessingException e) {
                    sb.append("Arg[" + key + "]:" + params.get(key).getClass().getName() + "类不处理");
                }
            });
        }
        return sb.toString();
    }

    private String getValue(String key, String value) {
        if (sensitiveKey.contains(key)) {
            return "******";
        }
        if (StrUtil.isNotBlank(value) && value.length() >= 300) {
            return value.substring(0, 300);
        }
        return value;
    }

    /**
     * 调用controller包下的任意类的任意方法时均会调用此方法+ @Around("execution(* com.company.controller.*.*(..))")
     */
    @Around("webLog()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        Object[] args = pjp.getArgs();
        // 得到其方法签名
        // MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        // 获取方法参数类型数组
        // Class[] paramTypeArray = methodSignature.getParameterTypes();
        // if (EntityManager.class.isAssignableFrom(paramTypeArray[paramTypeArray.length - 1])) {
        // //如果方法的参数列表最后一个参数是entityManager类型，则给其赋值
        // args[args.length - 1] = entityManager;
        // }
        // log.info("请求参数为{}", args);
        // 动态修改其参数
        long begin = System.nanoTime();
        // 注意，如果调用joinPoint.proceed()方法，则修改的参数值不会生效，必须调用joinPoint.proceed(Object[] args)
        Object result = pjp.proceed(args);
        long end = System.nanoTime();
        log.info("请求方法：{}，耗时：{} ns，cost：{} ms", pjp.getSignature().toString(), // Arrays.toString(pjp.getArgs()),
            (end - begin), (end - begin) / 1000000);
        // log.info("响应结果为{}", result);
        // 如果这里不返回result，则目标对象实际返回值会被置为null
        return result;
    }

}